/****************************************************************
 ** Product  :   HP Subscriber Network Application Policy
 ** Module   :   gr-manager
 ** Date: Apr 10, 2015               
 ** Author: Huang xiaoji
 ** (C) Copyright 2015, Hewlett-Packard Company, All Rights Reserved.
 ** This software is the proprietary information of HP, Inc.  
 ** Use is subject to license terms.
 *****************************************************************
 ** Description:   
 **    Deploy VoltDB catalog jar
 **    
 ****************************************************************/
package com.hp.snap.gr.action.impl;

import com.hp.snap.gr.action.ActionHandler;
import com.hp.snap.gr.exception.ActionException;
import com.hp.snap.gr.exception.RESTfulException;
import com.hp.snap.gr.model.Database;
import com.hp.snap.gr.model.Server;
import com.hp.snap.gr.model.jaxb.ActionType;
import com.hp.snap.gr.model.jaxb.GeneralActionType;
import com.hp.snap.gr.model.jaxb.ImportVoltDBConfigurationActionType;
import com.hp.snap.gr.model.jaxb.ParameterType;
import com.hp.snap.gr.utils.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.FileCopyUtils;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * Deploy VoltDB catalog jar by invoking the "/mgmt/catalogs" Restful interface, and then update database with new
 * catalog id by invoking "/mgmt/databases/" Restful interface.
 * 
 * There are some parameters need to be configured in the action: sourceDirectory, vemHost, vemPort, vemUri, database,
 * isDrAgentEnabled and isRestartDBAfterUpdate.
 * 
 * sourceDirectory:Import configuration source directory.
 * vemHost:  VoltDB database VEM host
 * vemPort: VoltDB database VEM port
 * vemUri: VoltDB database VEM RESTful URI
 * database: VoltDB database name list, Multiple value
 * isDrAgentEnabled: Flag to indicate if DrAgent is enabled or not
 * isRestartDBAfterUpdate: Flag to indicate if restart database after update catalog.jar
 * 
 * 
 * <P>
 * Version: 3.3 <br>
 * Author: Huang xiaoji 
 * Date: Apr 10, 2015
 * 
 * </P>
 * 
 **/
public class ImportVoltDBConfigurationActionHandler extends AbstractActionHandler {
    private static Logger logger = LoggerFactory.getLogger(ImportVoltDBConfigurationActionHandler.class);

    private String vemHost, vemUri;
    private int vemPort;
    private File sourceDir;
    private StopDRAgentActionHandler stopDrHandler = new StopDRAgentActionHandler();
    private StartDRAgentActionHandler startDrHandler = new StartDRAgentActionHandler();

    @Override
    public void execute(ActionType action, Map<String, Object> actionPlanInputs, Map<String, Object> actionPlanOutputs) throws ActionException {
        if (action instanceof ImportVoltDBConfigurationActionType) {
            ImportVoltDBConfigurationActionType actionType = (ImportVoltDBConfigurationActionType) action;
            vemHost = actionType.getVemHost();
            vemUri = actionType.getVemUri();
            vemPort = Integer.parseInt(actionType.getVemPort());
            sourceDir = new File(actionType.getSourceDirectory());
            if (!sourceDir.exists()) throw new ActionException("source directory is not exists");

            if (!actionType.getDatabase().isEmpty()) {
                Map<String, internal_database> databases = getDatabases();
                for (String dbname : actionType.getDatabase()) {
                    internal_database db = databases.get(dbname);
                    if (db == null) throw new ActionException("Can't find database " + dbname);
                    importDatabase(actionType, db, actionPlanInputs, actionPlanOutputs);
                }
            }
        }
    }

    private void importDatabase(ImportVoltDBConfigurationActionType actionType, internal_database db, Map<String, Object> actionPlanInputs, Map<String, Object> actionPlanOutputs) throws ActionException {
        String result = null;
        File catalogFile = new File(sourceDir, db.database.getName() + ".jar");
        if (!catalogFile.exists()) throw new ActionException(catalogFile.getAbsolutePath() + " is not exists");

        getCatalogJar(db);
        String oldCatalogMd5 = null;
        try {
            oldCatalogMd5 = DigestUtils.md5Hex(FileCopyUtils.copyToByteArray(catalogFile));
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
            throw new ActionException(e);
        }
        String newCatalogMd5 = getRemoteCatalogMd5(db);
        if (logger.isTraceEnabled()) {
            logger.trace("compare md5 old catalog is {} and new catalog is {}", oldCatalogMd5, newCatalogMd5);
        }
        if (!oldCatalogMd5.equalsIgnoreCase(newCatalogMd5)) {
            //catalog jar changed
            if (logger.isDebugEnabled()) {
                logger.debug("catalog jar {} has changed, will re-deploy catalog...", db.database.getName());
            }
            if (actionType.isIsDrAgentEnabled()) {
                //stop dr
                startOrStopDr(stopDrHandler, db.database.getName(), actionPlanInputs, actionPlanOutputs);
            }
            //replace catalog jar
            StringBuilder json = new StringBuilder("{\"Catalog\":{\"name\":\"");
            json.append(db.database.getName()).append("\",\"catalogbytes\":\"");
            try {
                byte[] bytes = FileCopyUtils.copyToByteArray(catalogFile);
                json.append(Base64.encodeBase64String(bytes)).append("\"}}");
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
                throw new ActionException(e);
            }

            result = RESTfulUtils.getRESTfulInvokeResult(vemHost, vemPort, vemUri + "/mgmt/catalogs",
                    Constants.HTTP_METHOD_POST, json.toString());

            long newCatalogId;
            try {
                JSONObject catalogObj = new JSONObject(result).getJSONObject("Catalog");
                newCatalogId = catalogObj.getLong("id");

                if (logger.isTraceEnabled()) {
                    logger.trace("uploaded catalog jar to voltdb, catalog id is {}", newCatalogId);
                }
            } catch (JSONException e) {
                logger.error(e.getMessage(), e);
                throw new ActionException(e);
            }
            if (actionType.isIsDrAgentEnabled() || actionType.isIsRestartDBAfterUpdate()) {
                //stop database
                VoltDBController.stopDatabase(vemHost, vemPort, vemUri, db.database);
            }
            //update database
            String body;
            result = RESTfulUtils.getRESTfulInvokeResult(vemHost, vemPort, vemUri + "/mgmt/databases/" + db.database.getId(),
                    Constants.HTTP_METHOD_GET, null);
            try {
                JSONObject item = new JSONObject(result);
                JSONObject dbObj = item.getJSONObject("Database");
                dbObj.put("catalog", String.valueOf(newCatalogId));

                body = item.toString();
            } catch (JSONException e) {
                logger.error(e.getMessage(), e);
                throw new ActionException(e);
            }
            if (logger.isTraceEnabled()) {
                logger.trace("database {} configuration changed to {}", db.database.getName(), body);
            }

            result = RESTfulUtils.getRESTfulInvokeResult(vemHost, vemPort, vemUri + "/mgmt/databases/" + db.database.getId(),
                    Constants.HTTP_METHOD_PUT, body);
            if (logger.isTraceEnabled()) {
                logger.trace("update database {} successful. result: {}", db.database.getName(), result);
            }
            //refresh database status
            db.database = RESTfulUtils.getVoltDBDatabase(db.database.getName());
            if (actionType.isIsDrAgentEnabled() || actionType.isIsRestartDBAfterUpdate()) {
                //start database
                VoltDBController.startDatabase(vemHost, vemPort, vemUri, db.database, true);
            }
            if (actionType.isIsDrAgentEnabled()) {
                //start dr
                startOrStopDr(startDrHandler, db.database.getName(), actionPlanInputs, actionPlanOutputs);
            }
            //delete old catalog
/*
            result = RESTfulUtils.getRESTfulInvokeResult(vemHost, vemPort, vemUri + "/mgmt/catalogs/" + db.database.getCatalog(),
                    Constants.HTTP_METHOD_DELETE, null);
            if (logger.isTraceEnabled()) {
                logger.trace("delete catalog {} successful. result: {}", db.database.getCatalog(), result);
            }
*/
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("catalog jar {} has no change, skip import voltdb configuration", db.database.getName());
            }
        }
    }

    private String getRemoteCatalogMd5(internal_database db) throws ActionException {
        SSHTools ssh = new SSHTools(null, null);
        try {
            String rs = ssh.cmd(vemHost, "ssh " + (StringUtils.isNotEmpty(db.firstServerSshkey)?
                    ("-i " + db.firstServerSshkey):"") + " " + (StringUtils.isNotEmpty(db.firstServerSshuser)?(db.firstServerSshuser + "@"):"") + db.firstServerIp + " md5sum " + db.jarfile + "|awk '{print $1}'");
            if (rs.startsWith("0|")) {
                return ssh.getCommandOutput(rs);
            } else {
                throw new ActionException("execute command via SSH failed: " + rs);
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new ActionException(e);
        }
    }

    private String getCatalogJar(internal_database db) throws ActionException {
        if (!db.database.getServers().isEmpty()) {
            for (Server server : db.database.getServers()) {
//                if (server.getStatus().equals(Constants.VDB_STATUS_ONLINE)) {
                if (StringUtils.isEmpty(db.firstServerIp)) {
                    getServerDetail(server, db);
                } else {
                    break;
                }
//                }
            }
            if (StringUtils.isEmpty(db.firstServerIp)) {
                throw new ActionException("There are no available db host in database " + db.database.getName());
            }
            String result = RESTfulUtils.getRESTfulInvokeResult(vemHost, vemPort, vemUri + "/mgmt/deployments/" + db.database.getDeployment(),
                    Constants.HTTP_METHOD_GET, null);
            try {
                JSONObject item = new JSONObject(result).getJSONObject("Deployment");
                db.jarfile = item.getString("voltroot") + "/" + db.database.getId() + "/catalog.jar";

                return db.jarfile;
            } catch (JSONException e) {
                logger.error(e.getMessage(), e);
                throw new ActionException(e);
            }
        } else {
            throw new ActionException("There are no available server in deployment");
        }
    }

    private void getServerDetail(Server server, internal_database db) {
        String result = null;
        try {
            result = RESTfulUtils.getRESTfulInvokeResult(vemHost, vemPort, vemUri + "/mgmt/servers/" + server.getServer(),
                    Constants.HTTP_METHOD_GET, null);
        } catch (RESTfulException e) {
            logger.warn(e.getMessage(), e);
            return;
        }

        try {
            JSONObject item = new JSONObject(result).getJSONObject("Server");
            db.firstServerIp = item.getString("host");
            db.firstServerSshuser = item.has("sshuser")?item.getString("sshuser"):"";
            db.firstServerSshkey = item.has("sshkey")?item.getString("sshkey"):"";
        } catch (JSONException e) {
            logger.warn(e.getMessage(), e);
        }
    }

    private Map<String, internal_database> getDatabases() throws ActionException {
        Map<String, Database> map = RESTfulUtils.getVoltDBDatabases(GRMConfigManager.getSiteName());
        Map<String, internal_database> rs = new HashMap<String, internal_database>();
        for (Map.Entry<String, Database> entry : map.entrySet()) {
            internal_database db = new internal_database();

            db.database = entry.getValue();
            rs.put(db.database.getName(), db);
        }
        return rs;
    }

    private void startOrStopDr(ActionHandler handler, String db, Map<String, Object> actionPlanInputs, Map<String, Object> actionPlanOutputs) throws ActionException {
        GeneralActionType actionType = new GeneralActionType();
        ParameterType p = new ParameterType();
        p.setName("DB_INSTANCE_NAME");
        p.setVal(db);
        actionType.getParameter().add(p);

        handler.execute(actionType, actionPlanInputs, actionPlanOutputs);
    }

    class internal_database {
        String jarfile;
        Database database;
        String firstServerIp = null, firstServerSshuser = null, firstServerSshkey = null;
    }
}
